<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * 基于数据库的函数库
 * 
 * @desc    亦称 Lib_MY_Library，专门处理基于数据库查询（起初是联查）的模型函数库（业务模型）
 * @version 1.0.0 
 * @author  Jason
 * @date	2017年6月9日 下午2:00:15
 */
abstract class Lib_database
{
	/** @var CI_Controller */
	protected $CI;
	
	/**
	 * 外键配置
	 * 
	 * @var array 三维数组, 每个第三维数组包括 5 个元素，分别是外键名，要联查的字段数组，模型对象引用，模型表映射，主键名
	 * eg: 如下
	 * ['$f_key', ['$f_field1', '$f_field2'], $p_model, '$p_mapping', '$p_key'],
	 * 
	$foreign_keys = [
			'master' => [
					['position_id', ['name', 'width', 'height'], $this->CI->mod_advertisement, 'position', 'id'],
					['member_id', ['name', 'email', 'mobile'], $this->CI->mod_member, 'master', 'id'],
					['member_id', ['nickname'], $this->CI->mod_member, 'info', 'id'],
			],
	];
	 */
	
	protected $foreign_keys = [
			'master' => [],
			'slaves' => [],
	];
	
	/**
	 * 时间字段配置
	 *
	 * @var array 三维数组, 每个第三维数组包括 2 个元素，分别是时间字段名，时间格式（标识）
	 * eg: 如下
	 * 
	$time_fields = [
			'master' => [
					['create_time', 'Y-m-d H:i:s'],
			],
	];
	 */
	protected $time_fields = [
			'master' => [],
			'slaves' => [],
	];
	
	/**
	 * 串行化字段配置
	 *
	 * @var array 三维数组, 每个第三维数组包括 2 个元素，分别是串行化字段名，串行化方法（标识）
	 * eg: 如下
	 * 
	$encode_fields = [
			'master' => [
					['images', 'implode'],			// 多图图片
					['license', 'implode'],			// 营业执照
					['languages', 'implode'],		// 导览语言
					['periods', 'serialize'],		// 时段设置：9:00-11:00,13:00-15:00（这个要序列化）
					['preferences', 'serialize'],	// 偏好设置
					['field_json_encoded', 'json_encode'],
			],
	];
	 */
	protected $encode_fields = [
			'master' => [],
			'slaves' => [],
	];
	
	public function __construct()
	{
		$this->CI =& get_instance();
	}
	
	/**
	 * 抽象方法，强制设置渲染的配置信息
	 * 
	 * @desc    抽象方法，强制设置渲染的配置信息：设置配置相关的成员属性。但暂时不这样做！
	 * @author  Jason
	 * @date    2017年6月9日 下午2:36:37
	 */
	#abstract function set_render_config();
	
	/**
	 * 渲染列表数据，当做也可以渲染单条数据，只需给第二个参数送 TRUE!
	 *
	 * @desc    渲染列表数据，当做也可以渲染单条数据，只需给第二个参数送 TRUE!
	 * 			渲染数据的方式主要包括以下几中形式：
	 * 			1. 关联外键查加信息 id_name_desc_...
	 * 			2. 时间字段处理
	 * 			3. 串行化字段解码
	 * 			
	 * 			另外，调用本方法之前，需要由各个子类去合法地设置渲染的配置信息，可在子类调用【设置渲染的配置信息】【set_render_config】的方法
	 * @version	1.0.0
	 * @author  Jason
	 * @date    2017年6月9日 下午2:23:29
	 * 
	 * @version	2.0.0
	 * @author  Jason
	 * @date	2017年7月12日 下午3:30:25
	 * @desc	DONE $mapping 参数修改成为一个对象，即直传配置信息！
	 *
	 * @param array   $data        数据列表和单项
	 * //@param string  $mapping     表映射，取配置用；TODONE 此参数可考虑修改成为一个对象，即直传配置信息！
	 * @param string  $config     配置，可以是表映射（从子类中取）；也可以直接是对象，客户端（主调）直传！成为一个对象，直传配置信息！
	 * @param boolean $is_only_one 是否是单项数据，默认为 false
	 * @param array   $callbacks   自定义的回调数组（回调未必是函数）
	 * 								如果上面的既定功能还不足以满足渲染需要，可以传自定义的回调，接收一个参数的、处理【单条数据】的、数组！
	 * 								程序会依次对每个回调进行调用，作用于列表中和每一项数据！
	 * @param array   $callbacks_多条？为什么没有处理多条数据的回调数组形参呢？——等这个函数返回了，自己爱怎么调就怎么调！
	 *
	 * @return  array	返回渲染过后的数组，一维或多维，取决于第三个参数
	 */
	public function render_list($data, $config = 'master', $is_only_one = false, $callbacks = [])
	{
		$is_only_one and $data = array($data);
		$render_data = [];
		
		// 配置信息，直接从参数取
		if (is_object($config)) {
			$foreign_keys = $config->foreign_keys;
			$time_fields = $config->time_fields;
			$encode_fields = $config->encode_fields;
		} else {
			// 配置信息，从子类中取
			$foreign_keys = $this->foreign_keys[$config];
			$time_fields = $this->time_fields[$config];
			$encode_fields = $this->encode_fields[$config];
		}
		
		$foreign_dict = [];
		foreach ($foreign_keys as $conf) {
			list($f_key, $f_fields, $p_model, $p_mapping, $p_key) = $conf;
			
			$select = $f_fields; array_push($select, $p_key);
			$select = implode(',', $select); // MARK db->select 多个字段时必须拼成字符串!
			# $ids = array_column($data, $f_key) and $ids[] = -1; // 用and不行！用or更好！！
			$ids = array_column($data, $f_key) or $ids[] = -1;
			$conditions = [
					['select', $select],
					['where_in', [$p_key, $ids]],
			];
			$p_dict = $p_model->select_instances_by_conditions($conditions, $p_mapping);
			$p_dict = array_column($p_dict, null, $p_key);
			$foreign_dict[] = $p_dict;
		}
		
		foreach ($data as $item) {
			// MARK 如果原来数据为空，那么也返回空，这样好处是可以判断结果是否为空
			if (empty($item)) {
				$render_data[] = $item;
				continue;
			}
			// 1. 关联外键查加信息
			foreach ($foreign_keys as $index => $conf) {
				list($f_key, $f_fields, , , $p_key) = $conf;
				$p_dict = @$foreign_dict[$index][$item[$f_key]];
				foreach ($f_fields as $field) {
					$item["{$f_key}_{$field}"] = $p_dict[$field];
				}
			}
			
			// 2. 时间字段处理
			foreach ($time_fields as $conf) {
				list($field, $format) = $conf;
				$item["{$field}_string"] = (!empty($item[$field])) ? date($format, $item[$field]) : null;
			}
			
			// 3. 串行化字段解码
			foreach ($encode_fields as $conf) {
				list($field, $format) = $conf;
				switch ($format) {
					case 'serialize':
						$item[$field] = @unserialize($item[$field]);
						break;
					case 'json_encode':
						$item[$field] = @json_decode($item[$field], TRUE);
						break;
					case 'implode':
						$item[$field] = array_values(array_filter(@explode(',', $item[$field]), 'strlen'));
						break;
				}
			}
			
			// 4. 自定义的单项回调数组
			foreach ($callbacks as $callback) {
				if (is_callable($callback)) {
					$item = call_user_func_array($callback, [$item]);
				}
			}
			
			$render_data[] = $item;
		}
		
		$is_only_one and $render_data = array_pop($render_data);
		
		return $render_data;
	}
}